Skip to main content

Cloudflare Zero Trust + Tunnel

This guide configures SSH access through Cloudflare Zero Trust and cloudflared so your server does not expose public SSH on 22/tcp.

Goal and outcome

  • SSH is reachable only through Cloudflare identity checks.
  • Server SSH listens on localhost (127.0.0.1) instead of public interfaces.
  • UFW does not allow inbound 22/tcp from the internet.

Prerequisites

  • Ubuntu VPS with sudo access.
  • Domain managed in Cloudflare.
  • Cloudflare Zero Trust account.
  • Local machine with ssh client.
  • Console/break-glass access from VPS provider in case of lockout.

Access model

Admin laptop
-> Cloudflare Access (identity policy)
-> Cloudflare Tunnel (cloudflared)
-> localhost SSH on VPS (127.0.0.1:22)

Step 1: Create an admin user and SSH key login

Create user and sudo access:

sudo adduser admin
sudo usermod -aG sudo admin

From your local machine, generate and copy key:

ssh-keygen -t ed25519 -C "admin@yourdomain.com"
ssh-copy-id admin@YOUR_SERVER_IP

Step 2: Harden SSH and bind to localhost

Backup config:

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F-%H%M%S)

Edit config:

sudo nano /etc/ssh/sshd_config

Set minimum required directives:

Port 22
ListenAddress 127.0.0.1

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
AuthenticationMethods publickey

Validate and reload:

sudo sshd -t && sudo systemctl restart ssh
sudo systemctl status ssh --no-pager

Step 3: Configure UFW

Do not open SSH publicly.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Confirm there is no 22/tcp allow rule from internet.

Step 4: Install cloudflared

curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared jammy main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update
sudo apt install -y cloudflared
cloudflared --version

If using Ubuntu 24.04, replace jammy with noble.

Step 5: Create tunnel and route DNS

Authenticate:

cloudflared tunnel login

Create tunnel:

cloudflared tunnel create ssh-tunnel

Map DNS hostname:

cloudflared tunnel route dns ssh-tunnel ssh.yourdomain.com

Step 6: Configure tunnel ingress for SSH

Create config file:

sudo mkdir -p /etc/cloudflared
sudo nano /etc/cloudflared/config.yml

Example config:

tunnel: ssh-tunnel
credentials-file: /home/admin/.cloudflared/<TUNNEL_ID>.json

ingress:
- hostname: ssh.yourdomain.com
service: ssh://127.0.0.1:22
- service: http_status:404

Install and start service:

sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared --no-pager

Step 7: Add Zero Trust Access policy

In Cloudflare Zero Trust dashboard:

  1. Create self-hosted application for ssh.yourdomain.com.
  2. Add Access policy (allow your identity/email group).
  3. Use OTP or your selected IdP.

Step 8: Client connection methods

Method A: cloudflared access ssh

cloudflared access ssh --hostname ssh.yourdomain.com

Method B: SSH ProxyCommand

Add to local ~/.ssh/config:

Host vps-cf
HostName ssh.yourdomain.com
User admin
ProxyCommand cloudflared access ssh --hostname %h

Connect:

ssh vps-cf

Verification checklist

  • ss -tulpen | grep :22 shows SSH bound to 127.0.0.1:22.
  • UFW does not expose 22/tcp publicly.
  • systemctl status cloudflared is active.
  • SSH works through Cloudflare Access flow.

Troubleshooting

Tunnel not connecting

sudo journalctl -u cloudflared -n 100 --no-pager

SSH denied after auth

  • Check sshd_config and key permissions.
  • Verify Access policy includes your user/group.

Lockout risk

  • Keep one active root/console session while testing.
  • Restore backup if needed:
sudo cp /etc/ssh/sshd_config.bak.* /etc/ssh/sshd_config
sudo systemctl restart ssh